<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>

/**
 * Experimental import that patches elements that interacts with Shady DOM 
 * such that tree traversal and mutation apis act like they would under
 * Shadow DOM.
 *
 * This import enables interaction with Shady DOM powered custom elements mostly
 * without the need to use `Polymer.dom` and can therefore enable better 
 * interoperation with 3rd party code, libraries, and frameworks that use DOM
 * tree manipulation apis.
 */

(function() {

  // ******* Only patch if we're using Shady DOM *******
  if (Polymer.Settings.useShadow) {
    return;
  }

  var baseFinishDistribute = Polymer.Base._finishDistribute;
  var TreeApi = Polymer.TreeApi;
  var DomApi = Polymer.DomApi;
  var dom = Polymer.dom;

  var nativeInsertBefore = Element.prototype.insertBefore;
  var nativeAppendChild = Element.prototype.appendChild;
  var nativeRemoveChild = Element.prototype.removeChild;

  // NOTE: any manipulation of a node must occur in a patched parent
  // so that the parent can cleanup the node's composed and logical 
  // information. We patch an element's parent's to make this rule more 
  // likely to be satisfied.
  // Also note that any use of qS/qSA must be done in a patched node.
  Polymer.Base._finishDistribute = function() {
    var hasDistributed = this.root._hasDistributed;
    baseFinishDistribute.call(this);
    if (!hasDistributed) {
      for (var n=this.firstChild; n; n=n.nextSibling) {
        Polymer.dom(n);
      };
      Polymer.dom(this);
      Polymer.dom(this.root);
      // TODO(sorvell): ensure top element's parents are wrapped (helped A2
      // since it uses qSA on the fragment containing stamped custom elements)
      // note that getOwnerRoot will patch all parents but there should be an 
      // explicit easier way to do this. 
      if (!this.dataHost) {
        Polymer.dom(this).getOwnerRoot();
      }
    }
  };


  var excluded = ['head'];

  Polymer.telemetry.patched = 0;
  
  var ctor = Polymer.DomApi.ctor;
  Polymer.DomApi.ctor = function(node) {
    Polymer.DomApi.patch(node);
    return new ctor(node);
  }

  Polymer.DomApi.ctor.prototype = ctor.prototype;

  Polymer.DomApi.patch = function(node) {
    if (!node.__patched &&
      (!node.localName || !excluded.indexOf(node.localName) >= 0)) {
      TreeApi.Logical.saveChildNodes(node);
      if (!TreeApi.Composed.hasParentNode(node)) {
        TreeApi.Composed.saveParentNode(node);
      }
      TreeApi.Composed.saveChildNodes(node);
      // TODO(sorvell): correctly patch non-element nodes.
      if (node.nodeType !== Node.TEXT_NODE) {
        node.__patched = true;
        patchImpl.patch(node);
      }
    }
  };

  Polymer.DomApi.ctor.prototype.unpatch = function() {
    this.flush();
    patchImpl.unpatch(this.node);
  };

  Polymer.DomApi.ctor.prototype.remove = function() {
    this.parentNode.removeChild(this.node);
    this.flush();
    this.unpatch();
  };

  // allows attribute setting to be patched  
  var nativeSetAttribute = Element.prototype.setAttribute;
  Polymer.DomApi.ctor.prototype.setAttribute = function(name, value) {
    nativeSetAttribute.call(this.node, name, value);
    this._maybeDistributeParent();
  };

  var nativeRemoveAttribute = Element.prototype.removeAttribute;
  Polymer.DomApi.ctor.prototype.removeAttribute = function(name, value) {
    nativeRemoveAttribute.call(this.node, name);
    this._maybeDistributeParent();
  };


  var log = false;

  var patchImpl = {
    
    hasPrototypeDescriptors: Boolean(Object.getOwnPropertyDescriptor(
      Node.prototype, 'textContent')),
    
    methods: ['appendChild', 'insertBefore', 'removeChild', 'replaceChild',
      'querySelector', 'querySelectorAll', 'getDestinationInsertionPoints', 
      'cloneNode', 'setAttribute', 'removeAttribute'],
    // <content>: getDistributedNodes

    accessors: ['parentNode', 'childNodes', 
      'firstChild', 'lastChild', 'nextSibling', 'previousSibling',
      'parentElement', 'children',
      'firstElementChild', 'lastElementChild', 
      'nextElementSibling', 'previousElementSibling'
    ],

    writableAccessors: ['textContent', 'innerHTML'],

    protoCache: {},
    
    patch: function(node) {
      Polymer.telemetry.patched++;
      log && console.warn('patch node', node);
      if (this.hasPrototypeDescriptors) {
        this.patchPrototype(node);
      } else {
        this.patchObject(node, true);
      }
    },

    patchPrototype: function(node) {
      var name = node.is || (node.getAttribute && node.getAttribute('is')) ||
        node.localName || node.nodeType;
      var proto = this.protoCache[name];
      if (!proto) {
        proto = this.protoCache[name] = this.createPatchedPrototype(node);
      }
      node.__proto__ = proto;
    },

    createPatchedPrototype: function(node) {
      var sourceProto = node.__proto__;
      var newProto = Object.create(sourceProto);
      newProto.__sourceProto__ = sourceProto;
      this.patchObject(newProto);
      return newProto;
    },

    patchObject: function(obj, cacheAccesors) {
      this.methods.forEach(function(m) {
        this.patchMethod(obj, m);
      }, this);
      this.accessors.forEach(function(n) {
        this.patchAccessor(obj, n, false, cacheAccesors);
      }, this);
      this.writableAccessors.forEach(function(n) {
        this.patchAccessor(obj, n, true, cacheAccesors);
      }, this);
    },

    patchMethod: function(obj, name) {
      var orig = obj[name];
      obj['_$' + name + '$_'] = orig;
      obj[name] = function() {
        log && console.log(this, name, arguments);
        return dom(this)[name].apply(this.__domApi, arguments);
      };
    },

    patchAccessor: function(obj, name, writable, shouldCache) {
      if (shouldCache) {
        this.cacheAccessor(obj, name);
      }
      var info = {
        get: function() {
          return dom(this)[name];
        },
        configurable: true
      };
      if (writable) {
        info.set = function(value) {
          dom(this)[name] = value;
        };
      }
      Object.defineProperty(obj, name, info);
    },

    cacheAccessor: function(obj, name) {
      var cache = obj.__descriptorCache = obj.__descriptorCache || {};
      cache[name] = Object.getOwnPropertyDescriptor(obj, name);
    },

    unpatch: function(obj) {
      if (obj.__sourceProto__) {
        obj.__proto__ = obj.__sourceProto__;
      } else {
        this.methods.forEach(function(m) {
          this.unpatchMethod(obj, m);
        }, this);
        this.accessors.forEach(function(n) {
          this.unpatchAccessor(obj, n);
        }, this);
        this.writableAccessors.forEach(function(n) {
          this.unpatchAccessor(obj, n);
        }, this);
      }
      obj.__patched = false;
    },

    unpatchMethod: function(obj, name) {
      delete obj[name];
    },

    unpatchAccessor: function(obj, name) {
      var info = obj.__descriptorCache[name];
      Object.defineProperty(obj, name, info);
    }
    
  };

  Polymer.DomApi.patchImpl = patchImpl;

  // NOTE: patch logical implementations here so we can use 
  // composed getters
  // TODO(sorvell): may need to patch saveChildNodes if and only if the tree has
  // already been distributed.
  TreeApi.Logical.recordInsertBefore = function(node, container, ref_node) {
    container.__dom.childNodes = null;
    // handle document fragments
    if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
      // TODO(sorvell): remember this for patching:
      // the act of setting this info can affect patched nodes
      // getters; therefore capture childNodes before patching.
      for (var n=TreeApi.Composed.getFirstChild(node); n; 
        n=TreeApi.Composed.getNextSibling(n)) {
        this._linkNode(n, container, ref_node);  
      }
    } else {
      this._linkNode(node, container, ref_node);
    }
  }

  TreeApi.Logical.getParentNode = function(node) {
    return node.__dom && node.__dom.parentNode || 
      TreeApi.Composed.getParentNode(node);
  };

  TreeApi.Logical.getFirstChild = function(node) {
    return node.__dom && node.__dom.firstChild || 
      TreeApi.Composed.getFirstChild(node);
  };

  TreeApi.Logical.getLastChild = function(node) {
    return node.__dom && node.__dom.lastChild || 
      TreeApi.Composed.getLastChild(node);
  };

  TreeApi.Logical.getNextSibling = function(node) {
    return node.__dom && node.__dom.nextSibling || 
      TreeApi.Composed.getNextSibling(node);
  };

  TreeApi.Logical.getPreviousSibling = function(node) {
    return node.__dom && node.__dom.previousSibling || 
      TreeApi.Composed.getPreviousSibling(node);
  };

  TreeApi.Logical.getFirstElementChild = function(node) {
    return node.__dom && node.__dom.firstChild ? 
      this._getFirstElementChild(node) :
      TreeApi.Composed.getFirstElementChild(node);
  };

  TreeApi.Logical.getLastElementChild = function(node) {
    return node.__dom && node.__dom.lastChild ? 
      this._getLastElementChild(node) :
      TreeApi.Composed.getLastElementChild(node);
  };

  TreeApi.Logical.getNextElementSibling = function(node) {
    return node.__dom && node.__dom.nextSibling ? 
      this._getNextElementSibling(node) :
      TreeApi.Composed.getNextElementSibling(node);
  };

  TreeApi.Logical.getPreviousElementSibling = function(node) {
    return node.__dom && node.__dom.previousSibling ? 
      this._getPreviousElementSibling(node) :
      TreeApi.Composed.getPreviousElementSibling(node);
  };


  // TODO(sorvell): This is largely copy/pasted from the Logical tree
  // implementation. The code could be factored such that it could be shared
  // but there are perf trade offs to consider.
  TreeApi.Composed = {

    hasParentNode: function(node) {
      return Boolean(node.__dom && node.__dom.$parentNode !== undefined);
    },

    hasChildNodes: function(node) {
      return Boolean(node.__dom && node.__dom.$childNodes !== undefined);
    },

    getChildNodes: function(node) {
      return this.hasChildNodes(node) ? this._getChildNodes(node) :
        (!node.__patched && TreeApi.arrayCopy(node.childNodes));
    },

    _getChildNodes: function(node) {
      if (!node.__dom.$childNodes) {
        node.__dom.$childNodes = [];
        for (var n=node.__dom.$firstChild; n; n=n.__dom.$nextSibling) {
          node.__dom.$childNodes.push(n);
        } 
      }
      return node.__dom.$childNodes;
    },

    getComposedChildNodes: function(node) {
      return node.__dom.$childNodes;
    },

    getParentNode: function(node) {
      return this.hasParentNode(node) ? node.__dom.$parentNode :
        (!node.__patched && node.parentNode);
    },

    getFirstChild: function(node) {
      return node.__patched ? node.__dom.$firstChild : node.firstChild;
    },

    getLastChild: function(node) {
      return node.__patched ? node.__dom.$lastChild : node.lastChild;
    },

    getNextSibling: function(node) {
      return node.__patched ? node.__dom.$nextSibling : node.nextSibling;
    },

    getPreviousSibling: function(node) {
      return node.__patched ? node.__dom.$previousSibling : node.previousSibling;
    },

    getFirstElementChild: function(node) {
      return node.__patched ? this._getFirstElementChild(node) : 
        node.firstElementChild;
    },

    _getFirstElementChild: function(node) {
      var n = node.__dom.$firstChild;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.$nextSibling;
      }
      return n;
    },

    getLastElementChild: function(node) {
      return node.__patched ? this._getLastElementChild(node) : 
        node.firstElementChild;
    },

    _getLastElementChild: function(node) {
      var n = node.__dom.$lastChild;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.$previousSibling;
      }
      return n;
    },

    getNextElementSibling: function(node) {
      return node.__patched ? this._getNextElementSibling(node) : 
        node.nextElementSibling;
    },

    _getNextElementSibling: function(node) {
      var n = node.__dom.$nextSibling;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.$nextSibling;
      }
      return n;
    },

    getPreviousElementSibling: function(node) {
      return node.__patched ? this._getPreviousElementSibling(node) : 
        node.previousElementSibling;
    },

    _getPreviousElementSibling: function(node) {
      var n = node.__dom.$previousSibling;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.$previousSibling;
      }
      return n;
    },
    
    saveChildNodes: function(node) {
      if (!this.hasChildNodes(node)) {
        node.__dom = node.__dom || {};
        node.__dom.$firstChild = node.firstChild;
        node.__dom.$lastChild = node.lastChild;
        node.__dom.$childNodes = [];
        for (var n=node.firstChild; n; n=n.nextSibling) {
          n.__dom = n.__dom || {};
          n.__dom.$parentNode = node;
          node.__dom.$childNodes.push(n);
          n.__dom.$nextSibling = n.nextSibling;
          n.__dom.$previousSibling = n.previousSibling;
        }
      }
    },

    recordInsertBefore: function(node, container, ref_node) {
      container.__dom.$childNodes = null;
      // handle document fragments
      if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
        // TODO(sorvell): remember this for patching:
        // the act of setting this info can affect patched nodes
        // getters; therefore capture childNodes before patching.
        for (var n=this.getFirstChild(node); n; n=this.getNextSibling(n)) {
          this._linkNode(n, container, ref_node);  
        }
      } else {
        this._linkNode(node, container, ref_node);
      }
    },

    _linkNode: function(node, container, ref_node) {
      node.__dom = node.__dom || {};
      container.__dom = container.__dom || {};
      if (ref_node) {
        ref_node.__dom = ref_node.__dom || {};
      }      
      // update ref_node.previousSibling <-> node
      node.__dom.$previousSibling = ref_node ? ref_node.__dom.$previousSibling : 
        container.__dom.$lastChild;
      if (node.__dom.$previousSibling) {
        node.__dom.$previousSibling.__dom.$nextSibling = node;
      }
      // update node <-> ref_node
      node.__dom.$nextSibling = ref_node;
      if (node.__dom.$nextSibling) {
        node.__dom.$nextSibling.__dom.$previousSibling = node;
      }
      // update node <-> container
      node.__dom.$parentNode = container;
      if (ref_node) {
        if (ref_node === container.__dom.$firstChild) {
          container.__dom.$firstChild = node;
        }
      } else {
        container.__dom.$lastChild = node;
        if (!container.__dom.$firstChild) {
          container.__dom.$firstChild = node;
        }
      }
      // remove caching of childNodes
      container.__dom.$childNodes = null;
    },

    recordRemoveChild: function(node, container) {
      node.__dom = node.__dom || {};
      container.__dom = container.__dom || {};
      if (node === container.__dom.$firstChild) {
        container.__dom.$firstChild = node.__dom.$nextSibling;
      }
      if (node === container.__dom.$lastChild) {
        container.__dom.$lastChild = node.__dom.$previousSibling;
      }
      var p = node.__dom.$previousSibling;
      var n = node.__dom.$nextSibling;
      if (p) {
        p.__dom.$nextSibling = n;
      }
      if (n) {
        n.__dom.$previousSibling = p;
      }
      node.__dom.$parentNode = node.__dom.$previousSibling = 
        node.__dom.$nextSibling = null;
      // remove caching of childNodes
      container.__dom.$childNodes = null;
    },

    // composed tracking needs to reset composed children here in case
    // they may have already been set (this shouldn't happen but can 
    // if dependency ordering is incorrect and as a result upgrade order 
    // is unexpected)
    clearChildNodes: function(node) {
      if (node.__dom && node.__dom.$parentNode) {
        node.__dom.$parentNode.__dom.$childNodes = [];
      }
      node.textContent = '';
    },

    saveParentNode: function(node) {
      node.__dom = node.__dom || {};
      node.__dom.$parentNode = node.parentNode;
    },

    insertBefore: function(parentNode, newChild, refChild) {
      this.saveChildNodes(parentNode);
      // remove from current location.
      if (this.hasParentNode(newChild)) {
        var oldParent = this.getParentNode(newChild);
        if (oldParent) {
          this._removeChild(oldParent, newChild);
        }
      }
      this._addChild(parentNode, newChild, refChild);
      return nativeInsertBefore.call(parentNode, newChild, refChild || null);
    },

    appendChild: function(parentNode, newChild) {
      this.saveChildNodes(parentNode);
      // remove from current location.
      if (this.hasParentNode(newChild)) {
        var oldParent = this.getParentNode(newChild);
        if (oldParent) {
          this._removeChild(oldParent, newChild);
        }
      }
      this._addChild(parentNode, newChild);
      return nativeAppendChild.call(parentNode, newChild);
    },

    removeChild: function(parentNode, node) {
      var currentParent = this.getParentNode(node);
      this.saveChildNodes(parentNode);
      this._removeChild(parentNode, node);
      if (currentParent === parentNode) {
        // TODO(sorvell); abort if the composedParent is not expected...
        if (!node.__patched && node.parentNode !== node.__dom.$parentNode) {
          return;
        }
        return nativeRemoveChild.call(parentNode, node);
      }
    },

    _addChild: function(parentNode, newChild, refChild) {
      if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
        var c$ = this.getChildNodes(newChild);
        for (var j=0; j < c$.length; j++) {
          this._addChild(parentNode, c$[j], refChild);
        }
      } else {
        newChild.__dom = newChild.__dom || {};
        newChild.__dom.$parentNode = parentNode;
        var c$ = this.getComposedChildNodes(parentNode);
        if (c$) {
          var i = c$.indexOf(refChild);
          i = i === -1 ? c$.length : i;
          c$.splice(i, 0, newChild);
        }
      }
    },

    _removeChild: function(parentNode, node) {
      node.__dom = node.__dom || {};
      node.__dom.$parentNode = null;
      var c$ = this.getComposedChildNodes(parentNode);
      if (c$) {
        var i = c$.indexOf(node);
        if (i >= 0) {
          c$.splice(i, 1);
        }
      }
    }

  };

  // patch important nodes
  if (window.document) {
    Polymer.dom(document);
    if (document.body) {
      Polymer.dom(document.body);
    }
  }

})();

</script>
